package de.tum.in.eist.im.server.Datastore;

import java.util.Date;

import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.FetchOptions;
import com.google.appengine.api.datastore.PreparedQuery;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Query.SortDirection;

import de.tum.in.eist.im.shared.Message.Message;
import de.tum.in.eist.im.shared.Security.SHAConverter;
import de.tum.in.eist.im.shared.User.User;

public class DatastoreManager implements DatastoreManagerInterface{

	// Create a new instance of the DatastoreService and save it in a private variable
	private final DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
	
	/**
	 * default constructor
	 */
	public DatastoreManager() {}
	
	// add a new user to the database
	public boolean addUser (User user) {
		Entity userEntity = new Entity("User");
		userEntity.setProperty("username", user.getUsername());
		userEntity.setProperty("password", user.getPassword());
		datastore.put(userEntity);
		return true;
	}
	 
	/**
	 * checks if a user is in the database
	 * @param user to check if he is in the database
	 * @return true
	 */
	public boolean checkForUser (User user) {
		Query q = new Query("User");
		q.addFilter("username", Query.FilterOperator.EQUAL, user.getUsername());
		PreparedQuery pq = datastore.prepare(q);
		
		if (pq.countEntities(FetchOptions.Builder.withLimit(1)) > 0)
			return true;
		else 
			return false;		
	}
	
	/**
	 * checks if the password of a user is the same as the password created in the parameter 
	 * @param user which should be validated for a correct login
	 * @return login successful
	 */
	public boolean userLoginValidation (User user) {
		Query q = new Query("User");
		q.addFilter("username", Query.FilterOperator.EQUAL, user.getUsername());
		
		PreparedQuery pq = datastore.prepare(q);
		
		String password = null;
		for (Entity result : pq.asIterable()) {
			password = (String) result.getProperty("password");
		}
		
		if (user.getPassword().equals(password))
			return true;
		else
			return false;
	}

	/**
	 * returns the id of an existing channel. if the channel doesn't exist so far it will be created
	 * @param channelTitle title of the new channel
	 * @return the id of the channel
	 */
	public long getChatChannelId (String channelTitle) {
		
		Query q = new Query("ChatChannel");
		q.addFilter("name", Query.FilterOperator.EQUAL, channelTitle);
		
		PreparedQuery pq = datastore.prepare(q);
		
		// check if channel already exist
		if (pq.countEntities(FetchOptions.Builder.withLimit(1)) > 0) {
			long channelId = -1;
			for (Entity result : pq.asIterable()) {
				channelId = result.getKey().getId();
			}
			return channelId;
		} else {
			// create new entity
			Entity chatChannelEntity = new Entity("ChatChannel");
			chatChannelEntity.setProperty("name", channelTitle);
			datastore.put(chatChannelEntity);
			
			// query new entity
			q = new Query("ChatChannel");
			q.addFilter("name", Query.FilterOperator.EQUAL, channelTitle);	
			pq = datastore.prepare(q);
			
			long channelId = -1;
			for (Entity result : pq.asIterable()) {
				channelId = result.getKey().getId();
			}
			return channelId;
		}
	}
	
	/**
	 * returns the id of the private chat channel. if the channel doesn't exist so far it will be created
	 * @param thisUser user calling the private chat channel conversation
	 * @param otherUser user called
	 * @return the id of the channel
	 */
	public long getPrivateChatChannelId (User thisUser, User otherUser) {
		Query q = new Query("PrivateChatChannel");
		q.addFilter("thisUser", Query.FilterOperator.EQUAL, thisUser.getUsername());
		q.addFilter("otherUser", Query.FilterOperator.EQUAL, otherUser.getUsername());
		
		PreparedQuery pq = datastore.prepare(q);
		
		// check if channel already exist
		if (pq.countEntities(FetchOptions.Builder.withLimit(1)) > 0) {
			long channelId = -1;
			for (Entity result : pq.asIterable()) {
				channelId = (Long) result.getProperty("channelId");
				break;
			}
			return channelId;
		} else {
			// create unique id
			long uniqueId = 0;
			try {
				uniqueId = (long)SHAConverter.toSHA256(new Date().toString()).hashCode();
			} catch (Exception e){}
			
			// TODO: if uniqueId already exists in db choose another one
			
			// create thisUser entry
			Entity chatChannelEntityThisUser = new Entity("PrivateChatChannel");
			chatChannelEntityThisUser.setProperty("thisUser", thisUser.getUsername());
			chatChannelEntityThisUser.setProperty("otherUser", otherUser.getUsername());
			chatChannelEntityThisUser.setProperty("channelId", uniqueId);
			datastore.put(chatChannelEntityThisUser);
			
			// create otherUser entry
			Entity chatChannelEntityOtherUser = new Entity("PrivateChatChannel");
			chatChannelEntityOtherUser.setProperty("thisUser", otherUser.getUsername());
			chatChannelEntityOtherUser.setProperty("otherUser", thisUser.getUsername());
			chatChannelEntityOtherUser.setProperty("channelId", uniqueId);
			datastore.put(chatChannelEntityOtherUser);
			
			// query new entity
			q = new Query("PrivateChatChannel");
			q.addFilter("thisUser", Query.FilterOperator.EQUAL, thisUser.getUsername());
			q.addFilter("otherUser", Query.FilterOperator.EQUAL, otherUser.getUsername());
			
			pq = datastore.prepare(q);
			
			long channelId = -1;
			for (Entity result : pq.asIterable()) {
				channelId = (Long) result.getProperty("channelId");
				break;
			}
			return channelId;
		}
	}
	
	/** 
	 * save a new message in the database
	 * @param message message to be stored
	 * @return true
	 */
	public boolean sendMessageToServer (Message message) {
		Entity messageEntity = new Entity("Message");
		
		messageEntity.setProperty("username", message.getMessageUser().getUsername());
		messageEntity.setProperty("text", message.getMessageText());
		messageEntity.setProperty("date", message.getMessageDate());
		messageEntity.setProperty("channelId", message.getMessageChatId());
		
		datastore.put(messageEntity);
		
		return true;
	}
	
	/**
	 * restore old messages of a channel
	 * @param chatChannelId the id of the channel
	 * @return an array with the old messages
	 */
	public Message[] getMessagesFromDatabase (Long chatChannelId) {
		Query q = new Query("Message");		
		q.addFilter("channelId", Query.FilterOperator.EQUAL, chatChannelId);
		q.addSort("date", SortDirection.ASCENDING);
		
		PreparedQuery pq = datastore.prepare(q);
		
		Message[] resultArray = new Message[pq.countEntities(FetchOptions.Builder.withDefaults())];
		int i = 0;
		for (Entity result : pq.asIterable()) {
			resultArray[i] = new Message((String)result.getProperty("text"),
										 new User((String)result.getProperty("username")),
										 (Date)result.getProperty("date"),
										 (Long)result.getProperty("channelId"));
			i++;
		}
		return resultArray;
	}
}
